﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace WC3Game
{
    public class PacketInformation
    {
        public byte[] IncompleteData = null;

        byte[] actionHeader = { 0x6b, 0x64, 0x72, 0x2e, 0x78, 0x00 };

#if DEBUG
        public String PortInfo = String.Empty;
#endif
        int processCount = 0;

        static byte[] slotInfoJoinData = null;
        static byte[] joinRequestData = null;

        public enum PacketProtocol : byte { TCP, UDP };

        byte[] payload = null;
        PacketProtocol protocol = PacketProtocol.TCP;

        public PacketProtocol Protocol
        {
            get { return protocol; }
        }

        public byte[] Payload
        {
            get { return payload; }
        }

        public String PayloadString
        {
            get { return payload.GetString(); }
        }

        public PacketInformation(PacketProtocol protocol, byte[] packetPayload)
        {
            this.protocol = protocol;
            this.payload = packetPayload;
        }

        public int Process(out int exceptionCount, SharpPcap.Packets.Util.Timeval time)
        {
            int payloadSize = 0;

            int commandOffset = 0;

            List<byte[]> commands = new List<byte[]>();

            exceptionCount = 0;

            byte packetType = Payload[0];
            byte packetCommand = Payload[1];

            if (Enum.IsDefined(typeof(Session.GamePacketConstants), packetType))
            {
                switch ((Session.GamePacketConstants)packetType)
                {
                    case Session.GamePacketConstants.BNET_PROTOCOL:
                        if (!Enum.IsDefined(typeof(Session.BattleNetPacketIDConstants), packetCommand))
                            return -1;
                        break;
                    case Session.GamePacketConstants.W3GS_PROTOCOL:
                        if (!Enum.IsDefined(typeof(Session.W3GSPacketIDConstants), packetCommand))
                            return -1;
                        break;
                    default:
                        break;
                }

            }

            //try
            {
                do
                {
                    payloadSize = Payload.Range(2 + commandOffset).ToShort();
                    if (payloadSize <= 0 || payloadSize > Payload.Length)
                    {
                        // Not a game packet.
                        break;
                    }

                    byte[] command = Payload.Range(commandOffset, commandOffset + payloadSize);

                    if (command.Length == 0)
                    {
                        // Incomplete packet. Set up to wait for more data.
                        commands.Clear();
                        processCount = -1;
                        IncompleteData = Payload;
                        break;
                    }

                    if (command.Length >= 4)
                        commands.Add(command);

                    commandOffset += payloadSize;
                    processCount++;
                } while (commandOffset < Payload.Length);
            }
            //catch (IndexOutOfRangeException)
            //{
            //   Console.WriteLine("Exception parsing packet frame {0}", Session.FrameCounter);
            //    commandOffset++;
            //   exceptionCount++;
            //}

            foreach (byte[] command in commands)
                ProcessCommand(command, time);


            return processCount;
        }

        public void ProcessCommand(byte[] command, SharpPcap.Packets.Util.Timeval time)
        {
            Game currentGame = Session.CurrentGame;
            SharpPcap.Packets.Util.Timeval newTime = new SharpPcap.Packets.Util.Timeval(0, 0);

            if (currentGame != null)
            {

                newTime = new SharpPcap.Packets.Util.Timeval(time.Seconds - currentGame.StartTime.Seconds, 0);
            }

            if (command[0] == (byte)Session.GamePacketConstants.BNET_PROTOCOL)
            {
                #region Battle Net Packets
                if (protocol == PacketProtocol.TCP)
                {
#if DEBUG
                    Console.WriteLine("Command: {0}, Packet Frame: {1}", (Session.BattleNetPacketIDConstants)command[1], Session.FrameCounter);
#endif
                    switch ((Session.BattleNetPacketIDConstants)command[1])
                    {
                        case Session.BattleNetPacketIDConstants.SID_NULL:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_STOPADV:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_SERVERLIST:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLIENTID:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_STARTVERSIONING:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_REPORTVERSION:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_STARTADVEX:
                            break;
                        //case Session.BattleNetPacketIDConstants.SID_GETADVLISTEX:
                        //    break;
                        case Session.BattleNetPacketIDConstants.SID_JOINCHANNEL:
                        case Session.BattleNetPacketIDConstants.SID_ENTERCHAT:
                            if (Session.CurrentGame != null)
                            {
                                Game.SendGameStateChangeMessage(String.Format("Game ended @ {0} (incomplete)", newTime.ToTimeFormat()));
                                Session.AddCurrentGame();
                            }
                            break;
                        case Session.BattleNetPacketIDConstants.SID_GETCHANNELLIST:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CHATCOMMAND:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CHATEVENT:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_LEAVECHAT:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_LOCALEINFO:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_FLOODDETECTED:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_UDPPINGRESPONSE:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CHECKAD:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLICKAD:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_REGISTRY:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_MESSAGEBOX:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_STARTADVEX2:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_GAMEDATAADDRESS:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_STARTADVEX3:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_LOGONCHALLENGEEX:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLIENTID2:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_LEAVEGAME:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_DISPLAYAD:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_NOTIFYJOIN:
                            if (currentGame != null)
                                currentGame.SetGameName(command);
                            break;
                        case Session.BattleNetPacketIDConstants.SID_PING:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_READUSERDATA:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_WRITEUSERDATA:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_LOGONCHALLENGE:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_LOGONRESPONSE:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CREATEACCOUNT:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_SYSTEMINFO:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_GAMERESULT:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_GETICONDATA:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_GETLADDERDATA:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_FINDLADDERUSER:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CDKEY:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CHANGEPASSWORD:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CHECKDATAFILE:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_GETFILETIME:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_QUERYREALMS:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_PROFILE:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CDKEY2:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_LOGONRESPONSE2:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CHECKDATAFILE2:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CREATEACCOUNT2:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_LOGONREALMEX:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_STARTVERSIONING2:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_QUERYREALMS2:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_QUERYADURL:
                            break;
                        //case Session.BattleNetPacketIDConstants.SID_WARCRAFTGENERAL:
                        //    break;
                        case Session.BattleNetPacketIDConstants.SID_NETGAMEPORT:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_NEWS_INFO:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_OPTIONALWORK:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_EXTRAWORK:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_REQUIREDWORK:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_TOURNAMENT:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_AUTH_INFO:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_AUTH_CHECK:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_AUTH_ACCOUNTCREATE:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_AUTH_ACCOUNTLOGON:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_AUTH_ACCOUNTLOGONPROOF:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_AUTH_ACCOUNTCHANGE:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_AUTH_ACCOUNTCHANGEPROOF:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_AUTH_ACCOUNTUPGRADE:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_AUTH_ACCOUNTUPGRADEPROOF:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_SETEMAIL:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_RESETPASSWORD:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CHANGEEMAIL:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_SWITCHPRODUCT:
                            break;
                        // case Session.BattleNetPacketIDConstants.SID_WARDEN:
                        //    break;
                        case Session.BattleNetPacketIDConstants.SID_GAMEPLAYERSEARCH:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_FRIENDSLIST:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_FRIENDSUPDATE:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_FRIENDSADD:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_FRIENDSREMOVE:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_FRIENDSPOSITION:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANFINDCANDIDATES:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANINVITEMULTIPLE:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANCREATIONINVITATION:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANDISBAND:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANMAKECHIEFTAIN:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANINFO:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANQUITNOTIFY:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANINVITATION:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANREMOVEMEMBER:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANINVITATIONRESPONSE:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANRANKCHANGE:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANSETMOTD:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANMOTD:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANMEMBERLIST:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANMEMBERREMOVED:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANMEMBERSTATUSCHANGE:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANMEMBERRANKCHANGE:
                            break;
                        case Session.BattleNetPacketIDConstants.SID_CLANMEMBERINFORMATION:
                            break;
                        default:
                            Console.WriteLine("Discarded command. Packet Frame: {0}", Session.FrameCounter);
                            break;
                    }
                }

                if (protocol == PacketProtocol.UDP)
                {
#if DEBUG && SHOWCOMMANDPARSE
                    Console.WriteLine("Command: {0}, Packet Frame: {1}", (Session.BattleNetPacketIDConstants)command[1], Session.FrameCounter);
#endif
                    /*
                    switch ((Session.BattleNetPacketIDConstants)command[1])
                    {
                        case Session.BattleNetPacketIDConstants.PKT_CLIENTREQ:
                            break;
                        case Session.BattleNetPacketIDConstants.PKT_SERVERPING:
                            break;
                        case Session.BattleNetPacketIDConstants.PKT_KEEPALIVE:
                            break;
                        case Session.BattleNetPacketIDConstants.PKT_CONNTEST:
                            break;
                        case Session.BattleNetPacketIDConstants.PKT_CONNTEST2:
                            break;
                        default:
                            Console.WriteLine("Discarded command.");
                            break;
                    }
                     * */
                }
            }
                #endregion

            if (command[0] == (byte)Session.GamePacketConstants.W3GS_PROTOCOL)
            {
                #region W3GS Packets
                Player joiningPlayer = null;

#if DEBUG && SHOWCOMMANDPARSE
                Console.WriteLine("Command: {0}, Packet Frame: {1}", (Session.W3GSPacketIDConstants)command[1], Session.FrameCounter);
#endif

                switch ((Session.W3GSPacketIDConstants)command[1])
                {
                    case Session.W3GSPacketIDConstants.W3GS_PING_FROM_HOST:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_SLOTINFOJOIN:
                        slotInfoJoinData = command;

                        if (joinRequestData != null)
                        {
                            joiningPlayer = Player.FromJoinRequest(joinRequestData, command);

                            joinRequestData = null;
                            slotInfoJoinData = null;

                            if (currentGame == null)
                            {
                                Session.TemporaryPlayers.Add(
                                    joiningPlayer
                                    );
                            }
                            else
                            {
                                joiningPlayer.SetGame(currentGame);
                                currentGame.AddPlayer(joiningPlayer);
                            }
                        }

                        if (currentGame != null)
                        {
                            currentGame.GameSlots = GameSlot.DecodeSlots(command);
                        }
                        else
                        {
                            List<GameSlot> temp = GameSlot.DecodeSlots(command);
                        }
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_REJECTJOIN:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_PLAYERINFO:
                        if (currentGame == null)
                        {
                            Session.TemporaryPlayers.Add(
                                Player.CreatePlayer(command, currentGame)
                                );
                        }
                        else
                            currentGame.AddPlayer(command);
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_PLAYERLEAVE_OTHERS:
                        if (currentGame != null)
                            currentGame.RemovePlayer(command, newTime);
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_GAMELOADED_OTHERS:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_SLOTINFO:
                        if (currentGame != null)
                        {
                            currentGame.GameSlots = GameSlot.DecodeSlots(command);
                        }
                        else
                        {
                            List<GameSlot> temp = GameSlot.DecodeSlots(command);
                        }
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_COUNTDOWN_START:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_COUNTDOWN_END:
                        if (currentGame != null)
                        {
                            currentGame.IsStarted = true;
                            currentGame.StartTime = time;
                        }
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_INCOMING_ACTION:
                        int actionCount = ProcessActions(command, newTime);
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_CHAT_FROM_HOST:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_START_LAG:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_STOP_LAG:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_HOST_KICK_PLAYER:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_REQJOIN:
                        joinRequestData = command;

                        if (slotInfoJoinData != null)
                        {
                            joiningPlayer = Player.FromJoinRequest(joinRequestData, command);

                            joinRequestData = null;
                            slotInfoJoinData = null;

                            if (currentGame == null)
                            {
                                Session.TemporaryPlayers.Add(
                                    joiningPlayer
                                    );
                            }
                            else
                            {
                                joiningPlayer.SetGame(currentGame);
                                currentGame.AddPlayer(joiningPlayer);
                            }
                        }
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_LEAVEGAME:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_GAMELOADED_SELF:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_OUTGOING_ACTION:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_OUTGOING_KEEPALIVE:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_CHAT_TO_HOST:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_DROPREQ:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_SEARCHGAME:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_GAMEINFO:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_CREATEGAME:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_REFRESHGAME:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_DECREATEGAME:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_CHAT_OTHERS:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_PING_FROM_OTHERS:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_PONG_TO_OTHERS:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_MAPCHECK:
                        // Received upon joining game.
                        // Player joined a game. Create a new one.
                        if (currentGame == null)
                        {
                            currentGame = Session.CreateGame(command);

                            if (Session.TemporaryPlayers.Count > 0)
                            {
                                foreach (Player p in Session.TemporaryPlayers)
                                {
                                    if (p.Parent == null)
                                        p.SetGame(currentGame);

                                    currentGame.AddPlayer(p);
                                }
                                Session.TemporaryPlayers.Clear();
                            }
                        }
                        else
                        {
                            Session.AddCurrentGame();
                            currentGame = Session.CreateGame(command);
                        }
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_STARTDOWNLOAD:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_MAPSIZE:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_MAPPART:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_MAPPARTOK:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_MAPPARTNOTOK:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_PONG_TO_HOST:
                        break;
                    case Session.W3GSPacketIDConstants.W3GS_INCOMING_ACTION2:

                        int action2Count = ProcessActions(command, newTime);
                        break;
                    default:
                        Console.WriteLine("Discarded command. Packet Frame: {0}", Session.FrameCounter);
                        break;
                }
                #endregion
            }
        }



        private int ProcessActions(byte[] actionPacket, SharpPcap.Packets.Util.Timeval time)
        {
            int actionsFound = 0;
            bool itemsReset = false;

            int i = 0;

            while (actionPacket.Length >= i + 6)
            {
                byte[] actionDataHeader = actionPacket.Range(i, i + 6);

                bool headersEqual = true;

                for (int x = 0; x < 6; x++)
                {
                    if (actionDataHeader[x] != actionHeader[x])
                    {
                        headersEqual = false;
                        break;
                    }
                }

                if (headersEqual)
                {
                    String data = actionPacket.ExtractString(i + 6);
                    String key = actionPacket.ExtractString(i + 7 + data.Length);

                    //  int value2 = BitConverter.ToInt32(RawActionData.Range(i + 12 + data.Length + key.Length, i + 12 + data.Length + key.Length + 4), 0);
                    int value = BitConverter.ToInt32(actionPacket, i + 8 + data.Length + key.Length);

                    Regex regex = new Regex(@"\d_\d");
                    if (regex.IsMatch(key) || key == "9")
                    {
                        int start = i + 8 + data.Length + key.Length;
                        String itemVal = actionPacket.Range(start, start + 4).Reverse().ToArray().GetString();

#if DEBUG
                        Console.WriteLine("Data: {1}, Key: {0}, Value: {2}", key, data, itemVal);
#endif
                        //actions.Add(new Action(data, key, itemVal));
                        if (key != "9")
                        {
                            if (Session.CurrentGame != null && !itemsReset)
                            {
                                Player p = Session.CurrentGame.PlayerFromColor(Int32.Parse(data));
                                p.ClearItems();

                                itemsReset = true;
                            }
                        }

                        Action.Parse(key, data, itemVal, time);
                        actionsFound++;
                    }
                    else
                    {
#if DEBUG
                        Console.WriteLine("Data: {1}, Key: {0}, Value: {2}", key, data, value);
                        
#endif
                        //actions.Add(new Action(data, key, value));
                        Action.Parse(key, data, value.ToString(), time);
                        actionsFound++;
                    }

                    i += 8 + data.Length + key.Length + 4;
                }
                else
                    i++;
            }
            return actionsFound;
        }
    }
}
